﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2014-02-16 11:35
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BassDrop.Core.Common.Cloud.Requests.Assets;

namespace BassDrop.Core.Common.Cloud.Requests
{
	/// <summary>
	/// Base class of cloud requests
	/// </summary>
	public abstract class CloudRequestBase
	{
		#region Events

		/// <summary>
		/// Event fired when the request starts being executed
		/// </summary>
		public event EventHandler<CloudRequestStartedEventArgs> RequestExecutionStarted;

		/// <summary>
		/// Event fired when the request is successfully executed
		/// </summary>
		public event EventHandler<CloudRequestSucceededEventArgs> RequestSucceeded;

		/// <summary>
		/// Event fired when the request is attempted to be executed but fails
		/// </summary>
		public event EventHandler<CloudRequestErroredEventArgs> RequestErrored;

		/// <summary>
		/// Fired when the request is no longer going to be executed
		/// </summary>
		public event EventHandler<CloudRequestCanceledEventArgs> RequestCanceled;

		/// <summary>
		/// Fire a request execution started event with the passed arguments
		/// </summary>
		/// <param name="args"></param>
		protected void OnRequestExecutionStarted( CloudRequestStartedEventArgs args )
		{
			if ( RequestExecutionStarted != null )
			{
				RequestExecutionStarted( this, args );
			}
		}

		/// <summary>
		/// Fire a request succeeded event with the passed arguments
		/// </summary>
		/// <param name="args"></param>
		protected void OnRequestSucceeded( CloudRequestSucceededEventArgs args )
		{
			if ( RequestSucceeded != null )
			{
				RequestSucceeded( this, args );
			}
		}

		/// <summary>
		/// Fire a request errored event with the passed arguments
		/// </summary>
		/// <param name="args"></param>
		protected void OnRequestErrored( CloudRequestErroredEventArgs args )
		{
			if ( RequestErrored != null )
			{
				RequestErrored( this, args );
			}
		}

		/// <summary>
		/// Fire a request canceled event with the passed arguments
		/// </summary>
		/// <param name="args"></param>
		protected void OnRequestCanceled( CloudRequestCanceledEventArgs args )
		{
			if ( RequestCanceled != null )
			{
				RequestCanceled( this, args );
			}
		}

		#endregion Events

		/// <summary>
		/// 
		/// </summary>
		/// <param name="mergeable">Is this request allowed to be merged with others</param>
		protected CloudRequestBase( bool mergeable )
		{
			Mergeable = mergeable;
		}

		/// <summary>
		/// Is this request allowed to be merged with others
		/// </summary>
		public bool Mergeable
		{
			get;
			private set;
		}

		/// <summary>
		/// If this request has been merged into another, this is it
		/// </summary>
		public CloudRequestBase MergedInto
		{
			get;
			private set;
		}

		/// <summary>
		/// The exception thrown on the last attempt to execute or null
		/// </summary>
		public Exception Error
		{
			get;
			private set;
		}

		/// <summary>
		/// Can the error occured on the last attempt to execute be repaired / fixed on retry or null if no error occured
		/// </summary>
		public bool? CanRepairError
		{
			get;
			private set;
		}

		/// <summary>
		/// The parent of this request (if it is in a compound request)
		/// </summary>
		public CloudRequestBase Parent
		{
			get;
			protected set;
		}

		/// <summary>
		/// Tries to merge the passed request into this one.
		/// </summary>
		/// <param name="other"></param>
		/// <returns>True, if merging is successful and the passed request should not be executed.</returns>
		public bool TryMerge( CloudRequestBase other )
		{
			if ( !this.Mergeable || !other.Mergeable )
			{
				return false;
			}
			else if ( CanMerge( other ) )
			{
				Merge( other );
				return true;
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// Tries to execute this request.
		/// </summary>
		/// <param name="connection"></param>
		/// <returns>
		///		True if execution succeeded, false otherwise. 
		///		If it doesn't succeed, a RequestErrored event is fired and the Error and CanRepairError properties are set as well.
		///	</returns>
		public async Task<bool> TryExecute( CloudConnectionBase connection )
		{
			try
			{
				await Execute( connection );
			}
			catch ( Exception )
			{
				return false;
			}

			return true;
		}

		/// <summary>
		/// Removes this request from the queue, meaning it won't run if it hasn't started yet
		/// </summary>
		/// <param name="bySystem">the cancellation was requested by the system, not the user</param>
		public void Cancel( bool bySystem = false )
		{
			if ( CloudManager.Instance != null )
			{
				CloudManager.Instance.Cancel( this, bySystem );
			}
		}

		/// <summary>
		/// Registers that this request has been canceled by the user
		/// </summary>
		public void CanceledByUser()
		{
			OnRequestCanceled( new CloudRequestCanceledEventArgs( this, CloudRequestCanceledEventArgs.CancelationReason.User ) );
		}

		/// <summary>
		/// Registers that this request has been canceled by the system
		/// </summary>
		public void CanceledBySystem()
		{
			OnRequestCanceled( new CloudRequestCanceledEventArgs( this, CloudRequestCanceledEventArgs.CancelationReason.System ) );
		}

		/// <summary>
		/// Register that this request has been merged into another one.
		/// Call in your implementation of TryMerge
		/// </summary>
		/// <param name="other"></param>
		protected void RegisterMergeInto( CloudRequestBase other )
		{
			MergedInto = other;
			OnRequestCanceled( new CloudRequestCanceledEventArgs( this, CloudRequestCanceledEventArgs.CancelationReason.Merge ) );
		}

		/// <summary>
		/// Merges the passed request into this one.
		/// May throw various exceptions if it cannot be merged, call CanMerge first to check if it can be done.
		/// </summary>
		/// <param name="other"></param>
		protected void Merge( CloudRequestBase other )
		{
			DoMerge( other );
			other.RegisterMergeInto( this );
		}

		/// <summary>
		/// Executes this request on the passed connection
		/// </summary>
		/// <param name="connection"></param>
		protected async Task Execute( CloudConnectionBase connection )
		{
			try
			{
				Error = null;
				CanRepairError = null;

				OnRequestExecutionStarted( new CloudRequestStartedEventArgs( this, connection ) );

				await DoExecute( connection );
			}
			catch ( Exception e )
			{
				Error = e;
				CanRepairError = CanRepair( e );

				OnRequestErrored( new CloudRequestErroredEventArgs( this, (bool) CanRepairError, Error ) );

				throw e;
			}

			OnRequestSucceeded( new CloudRequestSucceededEventArgs( this ) );
		}

		/// <summary>
		/// Returns if the passed request can be merged into this one
		/// </summary>
		/// <param name="other"></param>
		/// <returns></returns>
		protected abstract bool CanMerge( CloudRequestBase other );

		/// <summary>
		/// Merges the passed request into this one.
		/// May throw various exceptions if it cannot be merged, call CanMerge first to check if it can be done.
		/// DO NOT CALL THIS DIRECTLY, USE Merge OR TryMerge
		/// </summary>
		/// <param name="other"></param>
		protected abstract void DoMerge( CloudRequestBase other );

		/// <summary>
		/// Determines if retrying the execution of this request may succeed given the passed
		/// exception has been thrown on the last try.
		/// </summary>
		/// <param name="e"></param>
		/// <returns></returns>
		protected abstract bool CanRepair( Exception e );

		/// <summary>
		/// Executes this request on the passed connection.
		/// DO NOT CALL THIS DIRECTLY, USE Execute or TryExecute
		/// </summary>
		/// <param name="connection"></param>
		protected abstract Task DoExecute( CloudConnectionBase connection );

		#region Static accessors to protected methods

		/// <summary>
		/// Returns if the second request can be merged into / with the first one.
		/// Calls obj.CanMerge( other )
		/// </summary>
		/// <param name="obj">the object receiving the function call</param>
		/// <param name="other">the parameter of the function call</param>
		/// <returns></returns>
		protected static bool CanMerge( CloudRequestBase obj, CloudRequestBase other )
		{
			return obj.CanMerge( other );
		}

		/// <summary>
		/// Merges the second request into the first one.
		/// May throw various exceptions if it cannot be merged, call CanMerge first to check if it can be done.
		/// Calls obj.Merge( other )
		/// </summary>
		/// <param name="obj">the object receiving the function call</param>
		/// <param name="other">the parameter of the function call</param>
		/// <returns></returns>
		protected static void Merge( CloudRequestBase obj, CloudRequestBase other )
		{
			obj.Merge( other );
		}

		/// <summary>
		/// Executes the passed request on the passed connection
		/// Calls obj.Execute( connection )
		/// </summary>
		/// <param name="obj">the object receiving the function call</param>
		/// <param name="connection">the parameter of the function call</param>
		/// <returns></returns>
		protected static async Task Execute( CloudRequestBase obj, CloudConnectionBase connection )
		{
			await obj.Execute( connection );
		}

		/// <summary>
		/// Determines if retrying the execution of the passed request may succeed given the passed
		/// exception has been thrown on the last try.
		/// Calls obj.CanRepair( e )
		/// </summary>
		/// <param name="obj">the object receiving the function call</param>
		/// <param name="e">the parameter of the function call</param>
		/// <returns></returns>
		protected static bool CanRepair( CloudRequestBase obj, Exception e )
		{
			return obj.CanRepair( e );
		}

		#endregion Static accessors to protected methods
	}
}
